import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import javax.swing.DefaultComboBoxModel; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDesktopPane; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; import javax.swing.text.BadLocationException; import com.xerox.amazonws.simpledb.Domain; import com.xerox.amazonws.simpledb.DomainMetadata; import com.xerox.amazonws.simpledb.Item; import com.xerox.amazonws.simpledb.ItemVO; import com.xerox.amazonws.simpledb.SDBException; import com.xerox.amazonws.simpledb.SDBListResult; import com.xerox.amazonws.simpledb.SDBResult; import com.xerox.amazonws.simpledb.SimpleDB; public class QueryTool extends JPanel implements ActionListener { private JFrame parent; private JComboBox regionList; private JComboBox domainList; private JTextArea querySpace; private JDesktopPane results; private SimpleDB sdb; private Domain dom; public QueryTool(JFrame parent, String accessId, String secretKey, String proxyHost, int proxyPort) { this.parent = parent; sdb = new SimpleDB(accessId, secretKey); if (proxyHost != null) { sdb.getConnectionDelegate().setProxyValues(proxyHost, proxyPort); } layoutGUI(); loadPrefs(); } public void shutdown() { savePrefs(); } public void setDomain(String name) { try { dom = sdb.getDomain(name); } catch (SDBException ex) { System.err.println("Could not create domain object: "+ex.getMessage()); System.exit(-1); } } private void layoutGUI() { setLayout(new GridBagLayout()); JButton runQuery = new JButton("run"); runQuery.addActionListener(this); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; add(runQuery, gbc); JButton sponsor = new JButton("Sponsored by:", new ImageIcon("build/classes/dtlogo150.png")); sponsor.setHorizontalTextPosition(SwingConstants.LEFT); sponsor.setBorderPainted(false); sponsor.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { JOptionPane.showInternalMessageDialog(results, "<html><center>Work on QueryTool has been generously sponsored by directThought.<br>Please consider using them for your next project.<br>See <a href=\"http://directThought.com/\">directThought.com</a> for more information.</center></html>", dom.getName()+" metadata", JOptionPane.PLAIN_MESSAGE); } }); gbc = new GridBagConstraints(); add(sponsor, gbc); JLabel regLab = new JLabel("Region:"); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 1.0; add(regLab, gbc); regionList = new JComboBox(new String [] {"US", "EU"}); regionList.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { String endpoint = "sdb.amazonaws.com"; if (regionList.getSelectedItem().equals("EU")) { endpoint = "sdb.eu-west-1.amazonaws.com"; } sdb.setEndpoint(endpoint); populateDomainList(); setDomain((String)domainList.getSelectedItem()); } }); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.EAST; add(regionList, gbc); JLabel domLab = new JLabel("Domain:"); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 1.0; add(domLab, gbc); domainList = new JComboBox(); domainList.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { setDomain((String)domainList.getSelectedItem()); } }); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.EAST; add(domainList, gbc); populateDomainList(); setDomain((String)domainList.getSelectedItem()); JButton metadata = new JButton("Get Metadata"); metadata.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { try { DomainMetadata dm = dom.getMetadata().getResult(); StringBuilder dmOutput = new StringBuilder(); dmOutput.append("Item Count : "); dmOutput.append(dm.getItemCount()); dmOutput.append("\nAttr Name Count : "); dmOutput.append(dm.getAttributeNameCount()); dmOutput.append("\nAttr Value Count : "); dmOutput.append(dm.getAttributeValueCount()); dmOutput.append("\nItem Names Size : "); dmOutput.append(dm.getItemNamesSizeBytes()); dmOutput.append("\nAttribute Names Size : "); dmOutput.append(dm.getAttributeNamesSizeBytes()); dmOutput.append("\nAttribute Value Size : "); dmOutput.append(dm.getAttributeValuesSizeBytes()); int result = JOptionPane.showInternalOptionDialog(results, dmOutput.toString(), dom.getName()+" Domain", JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String [] {"Delete", "Close"}, "Close"); if (result == 0) { result = JOptionPane.showInternalConfirmDialog(results, "Do you really want to delete domain : "+dom.getName(), "Delete "+dom.getName(), JOptionPane.YES_NO_OPTION); if (result == JOptionPane.YES_OPTION) { sdb.deleteDomain(dom); dom = null; populateDomainList(); setDomain((String)domainList.getSelectedItem()); } } } catch (SDBException ex) { System.err.println("Problem fetching metadata or deleting domain: "+ex.getMessage()); } } }); gbc = new GridBagConstraints(); add(metadata, gbc); JButton newDom = new JButton("New Domain"); newDom.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { try { String result = JOptionPane.showInternalInputDialog(results, "Enter the name of the domain you'd like to create."); if (result != null) { dom = sdb.createDomain(result).getResult(); populateDomainList(); } } catch (SDBException ex) { System.err.println("Problem creating domain : "+ex.getMessage()); } } }); gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; add(newDom, gbc); querySpace = new JTextArea(); querySpace.setFont(new Font("monospaced", Font.PLAIN, 14)); querySpace.addKeyListener(new KeyAdapter() { public void keyTyped(KeyEvent evt) { if (evt.isControlDown() && evt.getKeyChar() == '\r') { executeQuery(); } } }); results = new JDesktopPane(); JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(querySpace), results); querySpace.setMinimumSize(new Dimension(100, 100)); gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; add(split, gbc); } public void populateDomainList() { String [] domainNames = new String [] {"no domains found"}; try { List<Domain> list = sdb.listDomains().getItems(); ArrayList<String> tmp = new ArrayList<String>(); for (Domain d : list) { tmp.add(d.getName()); } domainNames = tmp.toArray(domainNames); } catch (SDBException ex) { System.err.println("problem communicating with SimpleDB: "+ex.getMessage()); System.err.println(ex.getCause().getMessage()); } domainList.setModel(new DefaultComboBoxModel(domainNames)); } public void actionPerformed(ActionEvent evt) { executeQuery(); } private void executeQuery() { try { int lineNum = querySpace.getLineOfOffset(querySpace.getCaretPosition())+1; StringTokenizer st = new StringTokenizer(querySpace.getText(), "\n", true); int lineCount = 0; String val = ""; while (st.hasMoreTokens()) { String tok = st.nextToken(); if (tok.equals("\n")) { lineCount++; } else { val = tok; } if (lineCount == lineNum) break; } final String query = val; final ResultsFrame resultFrame = new ResultsFrame(query, dom); results.add(resultFrame, 1); try { resultFrame.setSelected(true); } catch (java.beans.PropertyVetoException ex) { } resultFrame.show(); // start fetching the data resultFrame.reload(); } catch (BadLocationException ex) { } } private void loadPrefs() { Properties props = new Properties(); try { props.load(new FileInputStream(System.getProperty("user.home", ".")+"/.typica.query.prefs")); querySpace.setText(props.getProperty("query.history")); domainList.setSelectedItem(props.getProperty("query.domain")); } catch (FileNotFoundException ioex) { // ignore.... might not be a file yet } catch (IOException ioex) { System.err.println("Error loading user preferences"); } } private void savePrefs() { Properties props = new Properties(); props.setProperty("query.history", querySpace.getText()); props.setProperty("query.domain", (String)domainList.getSelectedItem()); try { props.store(new FileOutputStream(System.getProperty("user.home", ".")+"/.typica.query.prefs"), "http://code.google.com/p/typica"); } catch (IOException ioex) { System.err.println("Error saving user preferences"); } } public static void main(String [] args) { final JFrame frame = new JFrame("SimpleDB Query Tool"); if (args.length != 2 && args.length != 4) { System.err.println("You must supply your access id and secret key on the command line"); System.err.println("Usage: QueryTool <AccessId> <SecretKey> [ProxyHost] [ProxyPort]"); System.exit(-1); } String host = null; int port = 1080; try { if (args.length == 4) { host = args[2]; port = Integer.parseInt(args[3]); } } catch (NumberFormatException ex) { System.err.println("proxy port not a valid integer, defaulting to "+port); } final QueryTool controls = new QueryTool(frame, args[0], args[1], host, port); Dimension size = controls.getPreferredSize(); frame.setSize(800, 600); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent event) { controls.shutdown(); System.exit(0); } }); frame.setContentPane(controls); frame.setVisible(true); } // a table model that shows items from SimpleDB public class ItemTableModel extends AbstractTableModel { private ArrayList<String> columns; private ArrayList<Item> items; public ItemTableModel() { clearData(); } public void addItems(List<Item> newItems) { // ensure all attrs have a column int firstRow = items.size(); boolean colsChanged = false; for (Item i : newItems) { for (String name : i.getAttributes().keySet()) { if (!columns.contains(name)) { columns.add(name); colsChanged = true; } } } items.addAll(newItems); if (colsChanged) { fireTableStructureChanged(); if (firstRow > 0) { fireTableRowsInserted(firstRow, items.size()); } } } public void clearData() { columns = new ArrayList<String>(); columns.add("itemName()"); items = new ArrayList<Item>(); } public int getRowCount() { return items.size(); } public int getColumnCount() { return columns.size(); } public Class getColumnClass(int column) { return String.class; } public String getColumnName(int column) { return columns.get(column); } public boolean isCellEditable(int row, int column) { return false; } public Object getValueAt(int row, int column) { if (column == 0) { return items.get(row).getIdentifier(); } else { String colName = columns.get(column); Set<String> vals = items.get(row).getAttributes().get(colName); if (vals == null) { return " -- "; } if (vals.size() == 1) { return vals.iterator().next(); } else { StringBuilder ret = new StringBuilder(); for (String val : vals) { ret.append(val); ret.append(","); } return ret.toString(); } } } } public class ResultsFrame extends JInternalFrame implements Runnable { private ItemTableModel tm; private double boxUsage = 0.0; private String time = "0"; private JLabel stats; private JButton reload; private JButton export; private boolean countMode; public JPopupMenu menu; public int row; public int col; public Domain domain; public ResultsFrame(String title, Domain domain) { super(title); this.domain = domain; setClosable(true); setIconifiable(true); setMaximizable(true); setResizable(true); setBounds(0, 0, 550, 300); setLayout(new BorderLayout()); JPanel topPan = new JPanel(new GridBagLayout()); reload = new JButton("reload") { public int getWidth() { return getPreferredSize().width; } public int getHeight() { return getPreferredSize().height; } }; reload.setEnabled(false); reload.setMargin(new Insets(0, 0, 0, 0)); reload.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { reload(); } }); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.5; gbc.insets = new Insets(0, 0, 0, 0); topPan.add(reload, gbc); export = new JButton("export") { public int getWidth() { return getPreferredSize().width; } public int getHeight() { return getPreferredSize().height; } }; export.setEnabled(false); export.setMargin(new Insets(0, 0, 0, 0)); export.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { export(); } }); topPan.add(export, gbc); stats = new JLabel("-"); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx = 1.0; gbc.insets = new Insets(0, 0, 0, 5); topPan.add(stats, gbc); add(topPan, BorderLayout.NORTH); tm = new ItemTableModel(); final JTable resultSpace = new JTable(tm); try { Class [] params = new Class[] { Boolean.TYPE }; JTable.class.getMethod("setAutoCreateRowSorter", params); resultSpace.setAutoCreateRowSorter(true); } catch (NoSuchMethodException ex) { } // ok, we simply won't sort resultSpace.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent evt) { row = resultSpace.rowAtPoint(evt.getPoint()); col = resultSpace.columnAtPoint(evt.getPoint()); menu.show(resultSpace, evt.getX(), evt.getY()); } }); JScrollPane sp = new JScrollPane(resultSpace); add(sp, BorderLayout.CENTER); menu = new JPopupMenu(); JMenuItem copy1 = new JMenuItem("copy cell"); copy1.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent evt) { copyValue((String)resultSpace.getModel().getValueAt(row, col)); } }); JMenuItem copy2 = new JMenuItem("copy row"); copy2.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent evt) { StringBuilder sb = new StringBuilder(); AbstractTableModel tm = (AbstractTableModel)resultSpace.getModel(); for (int i=0; i<tm.getColumnCount(); i++) { if (i > 0) sb.append("\t"); sb.append(tm.getValueAt(row, i)); } copyValue(sb.toString()); } }); menu.add(copy1); menu.add(copy2); } public void copyValue(String value) { Clipboard clip = getToolkit().getSystemClipboard(); StringSelection newData = new StringSelection(value); clip.setContents(newData, newData); } public void reload() { reload.setEnabled(false); export.setEnabled(false); boxUsage = 0.0; time = "0"; updateStats(); tm.clearData(); new Thread(this).start(); } public void export() { // file picker JFileChooser chooser = new JFileChooser("."); chooser.setMultiSelectionEnabled(false); int result = chooser.showSaveDialog(parent); if (result == JFileChooser.APPROVE_OPTION) { // scan table model and emit file File exportFile = chooser.getSelectedFile(); if (exportFile.exists()) { // ask about overwrite } try { PrintWriter pw = new PrintWriter(exportFile); int numColumns = tm.getColumnCount(); int numRows = tm.getRowCount(); for (int j=0; j<numColumns; j++) { pw.print(tm.getColumnName(j)); if (j < (numColumns-1)) { pw.print(","); } } pw.println(""); for (int i=0; i<numRows; i++) { for (int j=0; j<numColumns; j++) { pw.print(tm.getValueAt(i, j).toString()); if (j < (numColumns-1)) { pw.print(","); } } pw.println(""); } pw.close(); } catch (IOException ex) { System.err.println("Error exporting data: "+ex.getMessage()); } } } public void run() { StringBuilder resText = new StringBuilder(); try { int itemCount = 0; long start = System.currentTimeMillis(); String nextToken = null; countMode = (getTitle().indexOf("count(*)") > -1); long total = 0; do { SDBListResult<Item> sr = domain.selectItems(getTitle(), nextToken, false); List<Item> items = sr.getItems(); nextToken = sr.getNextToken(); itemCount += items.size(); if (countMode) { total += Long.parseLong(items.get(0).getAttribute("Count")); } updateResults(items); updateBoxUsage(sr.getBoxUsage()); if (itemCount > 1000) { nextToken = null; ArrayList<Item> trunc = new ArrayList<Item>(); trunc.add(domain.getItem("- truncated -").getResult()); updateResults(trunc); } } while (nextToken != null && !nextToken.trim().equals("")); long end = System.currentTimeMillis(); updateTime((end-start)/1000.0); SwingUtilities.invokeLater(new Runnable() { public void run() { reload.setEnabled(true); export.setEnabled(true); } }); if (countMode) { List<Item> tmp = new ArrayList<Item>(); Item i = new ItemVO("Total"); HashSet<String> vals = new HashSet<String>(); vals.add(""+total); i.getAttributes().put("Count", vals); tmp.add(i); updateResults(tmp); } } catch (SDBException ex) { resText.append(ex.getMessage()); } } void updateResults(final List<Item> data) { SwingUtilities.invokeLater(new Runnable() { public void run() { tm.addItems(data); updateStats(); } }); } void updateBoxUsage(final String usage) { SwingUtilities.invokeLater(new Runnable() { public void run() { try { double newUsage = Double.parseDouble(usage); boxUsage += newUsage; updateStats(); } catch (NumberFormatException ex) { System.err.println("error parsing box usage : "+ex.getMessage()); } } }); } void updateTime(final double timeVal) { SwingUtilities.invokeLater(new Runnable() { public void run() { time = ""+timeVal; updateStats(); } }); } private void updateStats() { String usage = ""+boxUsage; if (usage.length() > 10) usage = usage.substring(0, 10); stats.setText("Box Usage:"+usage+" Items:"+tm.getRowCount()+" Time To Run:"+time+" secs"); } } }